001 /* 002 * Copyright 2006 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.transit; 020 021 import java.net.URI; 022 import java.net.URL; 023 import java.net.URLConnection; 024 import java.io.InputStream; 025 import java.io.OutputStream; 026 import java.io.IOException; 027 import java.io.Writer; 028 import java.io.OutputStreamWriter; 029 030 import javax.xml.parsers.DocumentBuilder; 031 import javax.xml.parsers.DocumentBuilderFactory; 032 033 import net.dpml.util.ElementHelper; 034 import net.dpml.transit.info.TransitDirective; 035 import net.dpml.transit.info.CacheDirective; 036 import net.dpml.transit.info.HostDirective; 037 import net.dpml.transit.info.ProxyDirective; 038 import net.dpml.transit.info.LayoutDirective; 039 040 import net.dpml.lang.ValueDirective; 041 import net.dpml.util.DecodingException; 042 import net.dpml.util.Logger; 043 044 import org.xml.sax.ErrorHandler; 045 046 import org.w3c.dom.Element; 047 import org.w3c.dom.Document; 048 049 /** 050 * Utility class supporting the reading of Transit XML configurations. 051 * 052 * @author <a href="http://www.dpml.net">The Digital Product Meta Library</a> 053 * @version 1.0.0 054 */ 055 public class TransitBuilder 056 { 057 private static final String XML_HEADER = 058 "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"; 059 060 private static final String NAME = "transit"; 061 062 private static final String PUBLIC_ID = 063 "-//DPML//DTD Transit Configuration Version 1.0//EN"; 064 065 private static final String SYSTEM_ID = 066 "http://download.dpml.net/dtds/transit_1_0.dtd"; 067 068 private static final String RESOURCE = 069 "net/dpml/transit/transit_1_0.dtd"; 070 071 private static final String DOCTYPE = 072 "\n<!DOCTYPE " 073 + NAME 074 + " PUBLIC \"" 075 + PUBLIC_ID 076 + "\" \"" 077 + SYSTEM_ID 078 + "\" >"; 079 080 private static final DTD[] DTDS = new DTD[] 081 { 082 new DTD( 083 PUBLIC_ID, 084 SYSTEM_ID, 085 RESOURCE, null ) 086 }; 087 088 private static final DTDResolver DTD_RESOLVER = 089 new DTDResolver( DTDS, TransitBuilder.class.getClassLoader() ); 090 091 private Logger m_logger; 092 093 /** 094 * Creation of a new transit configuration builder. 095 * @param logger the assigned logging channel 096 */ 097 public TransitBuilder( Logger logger ) 098 { 099 m_logger = logger; 100 } 101 102 /** 103 * Construct a transit configuration from a supplied uri. 104 * @param url the configuration url 105 * @return the transit configuration 106 * @exception Exception if an error occurs during configuration loading 107 */ 108 public TransitDirective load( final URL url ) throws Exception 109 { 110 URLConnection connection = url.openConnection(); 111 InputStream input = connection.getInputStream(); 112 113 final DocumentBuilderFactory factory = 114 DocumentBuilderFactory.newInstance(); 115 factory.setValidating( true ); 116 factory.setNamespaceAware( true ); 117 factory.setExpandEntityReferences( true ); 118 DocumentBuilder builder = factory.newDocumentBuilder(); 119 builder.setEntityResolver( DTD_RESOLVER ); 120 ErrorHandler errors = new SaxMonitor( m_logger ); 121 builder.setErrorHandler( errors ); 122 123 final Document document = builder.parse( input ); 124 final Element root = document.getDocumentElement(); 125 return build( root ); 126 } 127 128 /** 129 * Write a transit directive to an output stream as XML. 130 * @param directive the directive to externalize 131 * @param output the output stream to write to 132 * @exception IOException if an I/O error occurs 133 */ 134 public void write( TransitDirective directive, OutputStream output ) throws IOException 135 { 136 final Writer writer = new OutputStreamWriter( output ); 137 try 138 { 139 writer.write( XML_HEADER ); 140 writer.write( DOCTYPE ); 141 142 CacheDirective cache = directive.getCacheDirective(); 143 String cachePath = cache.getCache(); 144 String cacheLayout = cache.getCacheLayout(); 145 writeHeader( writer, cachePath, cacheLayout ); 146 147 ProxyDirective proxy = directive.getProxyDirective(); 148 writeProxy( writer, proxy ); 149 150 String localPath = cache.getLocal(); 151 String localLayout = cache.getLocalLayout(); 152 writeLocal( writer, localPath, localLayout ); 153 154 HostDirective[] hosts = cache.getHostDirectives(); 155 writeHosts( writer, hosts ); 156 157 writeFooter( writer ); 158 writer.write( "\n" ); 159 } 160 finally 161 { 162 writer.flush(); 163 writer.close(); 164 } 165 } 166 167 //------------------------------------------------------------- 168 // internals supporting XML to directive transformation 169 //------------------------------------------------------------- 170 171 private TransitDirective build( Element root ) throws Exception 172 { 173 String name = root.getTagName(); 174 if( !NAME.equals( name ) ) 175 { 176 final String error = 177 "Invalid root element name [" 178 + name 179 + "]."; 180 throw new IOException( error ); 181 } 182 183 String cachePath = ElementHelper.getAttribute( root, "cache" ); 184 String cacheLayout = ElementHelper.getAttribute( root, "layout" ); 185 186 Element localElement = ElementHelper.getChild( root, "local" ); 187 String localPath = ElementHelper.getAttribute( localElement, "path" ); 188 String localLayout = ElementHelper.getAttribute( localElement, "layout" ); 189 190 Element proxyElement = ElementHelper.getChild( root, "proxy" ); 191 ProxyDirective proxy = buildProxyDirective( proxyElement ); 192 193 Element hostsElement = ElementHelper.getChild( root, "hosts" ); 194 HostDirective[] hosts = buildHosts( hostsElement ); 195 196 Element layoutsElement = ElementHelper.getChild( root, "layouts" ); 197 LayoutDirective[] layouts = buildLayouts( layoutsElement ); 198 199 // handlers TBD 200 201 CacheDirective cache = 202 new CacheDirective( 203 cachePath, cacheLayout, localPath, localLayout, 204 CacheDirective.EMPTY_LAYOUTS, hosts ); 205 return new TransitDirective( proxy, cache ); 206 } 207 208 private LayoutDirective[] buildLayouts( Element element ) throws Exception 209 { 210 if( null == element ) 211 { 212 return null; 213 } 214 else 215 { 216 Element[] layoutElements = ElementHelper.getChildren( element, "layout" ); 217 LayoutDirective[] layouts = new LayoutDirective[ layoutElements.length ]; 218 for( int i=0; i<layoutElements.length; i++ ) 219 { 220 Element elem = layoutElements[i]; 221 layouts[i] = buildLayout( elem ); 222 } 223 return layouts; 224 } 225 } 226 227 private LayoutDirective buildLayout( Element element ) throws Exception 228 { 229 String id = ElementHelper.getAttribute( element, "id" ); 230 String title = ElementHelper.getAttribute( element, "title" ); 231 Element codebase = ElementHelper.getChild( element, "codebase" ); 232 URI uri = decodeURI( codebase ); 233 ValueDirective[] values = getValueDirectives( codebase ); 234 return new LayoutDirective( id, title, uri, values ); 235 } 236 237 private URI decodeURI( Element element ) throws DecodingException 238 { 239 String uri = ElementHelper.getAttribute( element, "uri" ); 240 if( null == uri ) 241 { 242 final String error = "Missing uri attribute."; 243 throw new DecodingException( element, error ); 244 } 245 else 246 { 247 try 248 { 249 return new URI( uri ); 250 } 251 catch( Exception e ) 252 { 253 final String error = "Bad uri argument [" + uri + "]."; 254 throw new DecodingException( element, error ); 255 256 } 257 } 258 } 259 260 private ValueDirective[] getValueDirectives( Element element ) 261 { 262 if( null == element ) 263 { 264 return null; 265 } 266 else 267 { 268 Element[] valueElements = ElementHelper.getChildren( element, "value" ); 269 ValueDirective[] values = new ValueDirective[ valueElements.length ]; 270 for( int i=0; i<valueElements.length; i++ ) 271 { 272 Element elem = valueElements[i]; 273 values[i] = buildValue( elem ); 274 } 275 return values; 276 } 277 } 278 279 private ValueDirective buildValue( Element element ) 280 { 281 String target = ElementHelper.getAttribute( element, "target" ); 282 String method = ElementHelper.getAttribute( element, "method" ); 283 String value = ElementHelper.getAttribute( element, "value" ); 284 if( value != null ) 285 { 286 return new ValueDirective( target, method, value ); 287 } 288 else 289 { 290 ValueDirective[] values = getValueDirectives( element ); 291 return new ValueDirective( target, method, values ); 292 } 293 } 294 295 private ProxyDirective buildProxyDirective( Element element ) 296 { 297 if( null == element ) 298 { 299 return null; 300 } 301 else 302 { 303 String host = ElementHelper.getAttribute( element, "host" ); 304 Element credentialsElement = ElementHelper.getChild( element, "credentials" ); 305 String username = getUsername( credentialsElement ); 306 char[] password = getPassword( credentialsElement ); 307 String[] excludes = buildProxyExcludes( element ); 308 return new ProxyDirective( host, excludes, username, password ); 309 } 310 } 311 312 private String[] buildProxyExcludes( Element element ) 313 { 314 if( null == element ) 315 { 316 return null; 317 } 318 else 319 { 320 Element[] elements = ElementHelper.getChildren( element, "exclude" ); 321 String[] excludes = new String[ elements.length ]; 322 for( int i=0; i<excludes.length; i++ ) 323 { 324 Element elem = elements[i]; 325 excludes[i] = ElementHelper.getValue( elem ); 326 } 327 return excludes; 328 } 329 } 330 331 private HostDirective[] buildHosts( Element element ) 332 { 333 Element[] elements = ElementHelper.getChildren( element, "host" ); 334 HostDirective[] hosts = new HostDirective[ elements.length ]; 335 for( int i=0; i<hosts.length; i++ ) 336 { 337 Element elem = elements[i]; 338 String id = ElementHelper.getAttribute( elem, "id" ); 339 int priority = Integer.parseInt( ElementHelper.getAttribute( elem, "priority" ) ); 340 String url = ElementHelper.getAttribute( elem, "url" ); 341 String layout = ElementHelper.getAttribute( elem, "layout" ); 342 boolean enabled = ElementHelper.getBooleanAttribute( elem, "enabled" ); 343 boolean trusted = ElementHelper.getBooleanAttribute( elem, "trusted" ); 344 String index = ElementHelper.getAttribute( elem, "index" ); 345 String scheme = ElementHelper.getAttribute( elem, "scheme" ); 346 String prompt = ElementHelper.getAttribute( elem, "prompt" ); 347 Element credentialsElement = ElementHelper.getChild( elem, "credentials" ); 348 String username = getUsername( credentialsElement ); 349 char[] password = getPassword( credentialsElement ); 350 hosts[i] = 351 new HostDirective( 352 id, priority, url, index, username, password, enabled, trusted, 353 layout, scheme, prompt ); 354 } 355 return hosts; 356 } 357 358 private String getUsername( Element element ) 359 { 360 if( null == element ) 361 { 362 return null; 363 } 364 else 365 { 366 return ElementHelper.getAttribute( element, "username" ); 367 } 368 } 369 370 private char[] getPassword( Element element ) 371 { 372 if( null == element ) 373 { 374 return null; 375 } 376 else 377 { 378 String password = ElementHelper.getAttribute( element, "password" ); 379 if( null == password ) 380 { 381 return null; 382 } 383 else 384 { 385 return password.toCharArray(); 386 } 387 } 388 } 389 390 //------------------------------------------------------------- 391 // internals supporting directive to XML transformation 392 //------------------------------------------------------------- 393 394 private void writeHeader( Writer writer, String cache, String layout ) throws IOException 395 { 396 writer.write( "\n\n<" + NAME + " cache=\"" + cache + "\" layout=\"" + layout + "\">" ); 397 } 398 399 private void writeFooter( Writer writer ) throws IOException 400 { 401 writer.write( "\n</" + NAME + ">" ); 402 } 403 404 private void writeProxy( Writer writer, ProxyDirective proxy ) throws IOException 405 { 406 if( null != proxy ) 407 { 408 String host = proxy.getHost(); 409 String username = proxy.getUsername(); 410 String password = getPassword( proxy.getPassword() ); 411 String[] excludes = proxy.getExcludes(); 412 413 boolean credentials = ( ( null != username ) || ( null != password ) ); 414 415 if( excludes.length == 0 && ( !credentials ) ) 416 { 417 writer.write( 418 "\n <proxy host=\"" + host + "\"/>" ); 419 } 420 else 421 { 422 writer.write( "\n <proxy host=\"" + host + "\">" ); 423 if( credentials ) 424 { 425 writer.write( "\n <credentials" ); 426 if( null != username ) 427 { 428 writer.write( " username=\"" + username + "\"" ); 429 } 430 if( null != password ) 431 { 432 writer.write( " password=\"" + password + "\"" ); 433 } 434 writer.write( "/>" ); 435 } 436 if( excludes.length > 0 ) 437 { 438 writer.write( "\n <excludes>" ); 439 for( int i=0; i<excludes.length; i++ ) 440 { 441 String exclude = excludes[i]; 442 writer.write( "\n <exclude>" + exclude + "</exclude>" ); 443 } 444 writer.write( "\n </excludes>" ); 445 } 446 447 writer.write( "\n </proxy>" ); 448 } 449 } 450 } 451 452 private void writeLocal( Writer writer, String path, String layout ) throws IOException 453 { 454 writer.write( "\n <local path=\"" + path + "\" layout=\"" + layout + "\"/>" ); 455 } 456 457 private void writeHosts( Writer writer, HostDirective[] hosts ) throws IOException 458 { 459 writer.write( "\n <hosts>" ); 460 for( int i=0; i<hosts.length; i++ ) 461 { 462 HostDirective host = hosts[i]; 463 writeHost( writer, host ); 464 } 465 writer.write( "\n </hosts>" ); 466 } 467 468 private void writeHost( Writer writer, HostDirective host ) throws IOException 469 { 470 String id = host.getID(); 471 int priority = host.getPriority(); 472 String url = host.getHost(); 473 boolean enabled = host.getEnabled(); 474 boolean trusted = host.getTrusted(); 475 String layout = host.getLayout(); 476 String index = host.getIndex(); 477 String scheme = host.getScheme(); 478 String prompt = host.getPrompt(); 479 String username = host.getUsername(); 480 String password = getPassword( host.getPassword() ); 481 boolean credentials = ( ( null != username ) || ( null != password ) ); 482 483 writer.write( "\n <host id=\"" + id + "\" priority=\"" + priority + "\" url=\"" + url + "\"" ); 484 if( !enabled ) 485 { 486 writer.write( " enabled=\"false\"" ); 487 } 488 if( trusted ) 489 { 490 writer.write( " trusted=\"true\"" ); 491 } 492 if( null != layout ) 493 { 494 writer.write( " layout=\"" + layout + "\"" ); 495 } 496 if( null != index ) 497 { 498 writer.write( " index=\"" + index + "\"" ); 499 } 500 if( ( null != scheme ) && !scheme.equals( "" ) ) 501 { 502 writer.write( " scheme=\"" + scheme + "\"" ); 503 } 504 if( ( null != prompt ) && !prompt.equals( "" ) ) 505 { 506 writer.write( " prompt=\"" + prompt + "\"" ); 507 } 508 if( credentials ) 509 { 510 writer.write( "\n <credentials" ); 511 if( null != username ) 512 { 513 writer.write( " username=\"" + username + "\"" ); 514 } 515 if( null != password ) 516 { 517 writer.write( " password=\"" + password + "\"" ); 518 } 519 writer.write( "/>" ); 520 writer.write( "\n </host>" ); 521 } 522 else 523 { 524 writer.write( "/>" ); 525 } 526 } 527 528 private String getPassword( char[] password ) 529 { 530 if( null == password ) 531 { 532 return null; 533 } 534 else 535 { 536 return new String( password ); 537 } 538 } 539 }